﻿using System;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Linq;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

namespace InstanceController.ConfigurationDAL
{
	/// <summary>
	/// ConfigurationDataSource maps to the Azure tables and provides the CRUD methods.
	/// </summary>
	public class ConfigurationDataSource
	{
		private readonly ConfigurationDataServiceContext _projectDataServiceContext;

		/// <summary>
		/// Constructs the datasource. If azureTableExistsFlag is True no check is made, if 
		/// False it then checks to see if the table has previously been created. If not 
		/// then it creates the table using the data context. (Recommended for production code.)
		/// </summary>
		/// <param name="azureTableExistsFlag"></param>
		public ConfigurationDataSource(bool azureTableExistsFlag)
		{
			var storageAccount = AzureTableInitialization.GetAccount();
			_projectDataServiceContext = new ConfigurationDataServiceContext(storageAccount.TableEndpoint.ToString(),
																				   storageAccount.Credentials);
			//Fixed in Azure				//{
			//    // Optimization: Where table name and class name are not the same
			//    // ResolveEntityType is the name of the method that supplies the object type
			//    // To improve performance of ADO.NET Data Service deserialization 
			//    ResolveType = (unused) => typeof(ConfigurationDataModel)
			//    /*
			//     * This is being depreciated in Azure.  See this discussion:
			//     * http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/d84ba34b-b0e0-4961-a167-bbe7618beb83/
			//     * most recent are at the end of the list
			//     */
			//};

			// Create the table (see Global.asax.cs AzureTableInit.cs)
			if (!azureTableExistsFlag)
				storageAccount.CreateCloudTableClient().CreateTableIfNotExist(AzureTableInitialization.ConfigurationTableName);
		}

		/// <summary>
		/// Constructs the datasource. Always checks to see if the table has
		/// previously been created. If not then it creates the table 
		/// using the data context. (NOT recommended for production code.)
		/// </summary>
		public ConfigurationDataSource()
		{
			var storageAccount = AzureTableInitialization.GetAccount();
			_projectDataServiceContext = new ConfigurationDataServiceContext(storageAccount.TableEndpoint.ToString(),
																				   storageAccount.Credentials);
			//Fixed in Azure				//{
			//    // Optimization: Where table name and class name are not the same
			//    // ResolveEntityType is the name of the method that supplies the object type
			//    // To improve performance of ADO.NET Data Service deserialization 
			//    ResolveType = (unused) => typeof(ConfigurationDataModel)
			//};

			storageAccount.CreateCloudTableClient().CreateTableIfNotExist(AzureTableInitialization.ConfigurationTableName);
		}

		// Performing Entity Group Transactions
		#region CRUD Entity Group Transactions (Batch) http://msdn.microsoft.com/en-us/library/dd894038.aspx

		/// <summary>
		/// Add Inserts to a group of items. Returns the current number of items 
		/// tracked by the current context or -1 if the entity already exists in 
		/// the context object.
		/// </summary>
		/// <param name="newItems"></param>
		public int AddInsertToBatch(Object newItems)
		{
			return ConfigurationBatchOperation.AddBatchInsertEntityItem(newItems, _projectDataServiceContext);
		}

		/// <summary>
		/// Add Updates to a group of items. Returns the current number of items 
		/// tracked by the current context or -1 if the entity already exists in 
		/// the context object.
		/// </summary>
		/// <param name="itemToUpdate"></param>
		public int AddUpdateToBatch(Object itemToUpdate)
		{
			return ConfigurationBatchOperation.AddBatchUpdateEntityItem(itemToUpdate, _projectDataServiceContext);
		}

		/// <summary>
		/// Add Deletes to a group of items. Returns the current number of items 
		/// tracked by the current context or -1 if the entity already exists in 
		/// the context object.
		/// </summary>
		/// <param name="itemToDelete"></param>
		public int AddDeleteToBatch(Object itemToDelete)
		{
			return ConfigurationBatchOperation.AddBatchDeleteEntityItem(itemToDelete, _projectDataServiceContext);
		}

		/// <summary>
		/// Commits the entities in a batch to the Azure table storage. You cannot
		/// operate on a single entity more than once in a single batch commit. For
		/// example: Adding a partition/row and then subsequently deleting that row
		/// is not legal.  You must take precautions to ensure that the entities in
		/// the batch are unique. Attempts to commit with errors will be rejected.
		/// </summary>
		/// <returns></returns>
		public bool CommitBatch()
		{
			return ConfigurationBatchOperation.SaveBatch(_projectDataServiceContext);
		}

		#endregion

		#region CRUD methods (http://msdn.microsoft.com/en-us/library/dd179392.aspx)

		/// <summary>
		/// Create a new ConfigurationDataModel item.
		/// </summary>
		/// <param name="newItem">The new item.</param>
		/// <returns></returns>
		public bool Create(ConfigurationDataModel newItem)
		{
			if(newItem == null) return false;
			_projectDataServiceContext.AddObject(AzureTableInitialization.ConfigurationTableName, newItem);
			_projectDataServiceContext.SaveChanges();
			_projectDataServiceContext.Detach(newItem);
			return true;
		}

		#region READ methods (All, by Partition, by Partition/Row) http://msdn.microsoft.com/en-us/library/dd894039.aspx
		// Writing LINQ queries

		/// <summary>
		/// Read (select) all rows from the client table.
		/// </summary>
		/// <returns></returns>
		public IEnumerable<ConfigurationDataModel> Read()
		{
			_projectDataServiceContext.MergeOption = MergeOption.NoTracking;	// set when you will not be updating this entity
			IQueryable<ConfigurationDataModel> results;
			try
			{
				results = from c in _projectDataServiceContext.ConfigurationTable
				          select c;
			}
			catch (Exception)
			{
				return null;
			}

			var query = results.AsTableServiceQuery();
			var queryResults = query.Execute();

			return queryResults;
		}

		/// <summary>
		/// Read (select) all rows in the specified partition from the client table.
		/// </summary>
		/// <param name="partitionKey"></param>
		/// <returns></returns>
		public IEnumerable<ConfigurationDataModel> Read(string partitionKey)
		{
			if (String.IsNullOrEmpty(partitionKey)) return null;
			_projectDataServiceContext.MergeOption = MergeOption.NoTracking;	// set when you will not be updating this entity
			IQueryable<ConfigurationDataModel> results;
			try
			{
				results = from c in _projectDataServiceContext.ConfigurationTable
				              where c.PartitionKey == partitionKey
				              select c;
			}
			catch (Exception)
			{
				return null;
			}
			var query = results.AsTableServiceQuery();
			var queryResults = query.Execute();

			return queryResults;
		}

		/// <summary>
		/// Read (select) the specified row in the specified partition from the client table.
		/// </summary>
		/// <param name="partitionKey"></param>
		/// <param name="rowKey"></param>
		/// <returns></returns>
		public ConfigurationDataModel Read(string partitionKey, string rowKey)
		{
			if (String.IsNullOrEmpty(partitionKey) || String.IsNullOrEmpty(rowKey)) return null;
			_projectDataServiceContext.MergeOption = MergeOption.NoTracking;	// set when you will not be updating this entity
			ConfigurationDataModel results;
			try
			{
				results = (from c in _projectDataServiceContext.ConfigurationTable
							   where c.PartitionKey == partitionKey &&
									 c.RowKey == rowKey
							   select c).FirstOrDefault();
			}
			catch (Exception)
			{
				//if the specific row fails then this will return the first row in the table
				results = (from c in _projectDataServiceContext.ConfigurationTable
						   where c.PartitionKey == partitionKey
						   select c).FirstOrDefault();
			}

			return results;
		}

		#endregion

		/// <summary>
		/// Update a ConfigurationDataModel item.
		/// </summary>
		/// <param name="itemToUpdate">The item to update.</param>
		/// <returns></returns>
		public bool Update(ConfigurationDataModel itemToUpdate)
		{
			if (itemToUpdate == null) return false;
			//when using a context for updates/inserts/deletes, tracking has to be turned on and one would 
			//use PreseveChanges to ensure that etags are always updated for the entities
			try
			{
				_projectDataServiceContext.MergeOption = MergeOption.PreserveChanges;
				_projectDataServiceContext.AttachTo(AzureTableInitialization.ConfigurationTableName, itemToUpdate);
				_projectDataServiceContext.UpdateObject(itemToUpdate);
				_projectDataServiceContext.SaveChanges();
				_projectDataServiceContext.Detach(itemToUpdate);
			}
			catch (Exception)
			{
				_projectDataServiceContext.Detach(itemToUpdate);
				return false;
			}
			return true;
		}

		/// <summary>
		/// tells the server that an unconditional update needs to be performed i.e. go ahead and perform
		/// the requested operation irrespective of whether someone has changed the entity in the store.
		/// </summary>
		/// <param name="itemToUpdate">The item to update.</param>
		/// <returns></returns>
		public bool UpdateUnconditional(ConfigurationDataModel itemToUpdate)
		{
			if (itemToUpdate == null) return false;
			try
			{
				_projectDataServiceContext.AttachTo(AzureTableInitialization.ConfigurationTableName, itemToUpdate, "*"); //"*" = unconditional update
				_projectDataServiceContext.UpdateObject(itemToUpdate);
				_projectDataServiceContext.SaveChanges();
				_projectDataServiceContext.Detach(itemToUpdate);
			}
			catch (Exception)
			{
				_projectDataServiceContext.Detach(itemToUpdate);
				return false;
			}
			return true;
		}

		/// <summary>
		/// Delete a ConfigurationDataModel item.
		/// </summary>
		/// <param name="itemToDelete">The item to delete.</param>
		/// <returns></returns>
		public bool Delete(ConfigurationDataModel itemToDelete)
		{
			if (itemToDelete == null) return false;
			//when using a context for updates/inserts/deletes, tracking has to be turned on and one would 
			//use PreseveChanges to ensure that etags are always updated for the entities
			try
			{
				_projectDataServiceContext.MergeOption = MergeOption.PreserveChanges;
				_projectDataServiceContext.AttachTo(AzureTableInitialization.ConfigurationTableName, itemToDelete, "*");
				_projectDataServiceContext.DeleteObject(itemToDelete);
				_projectDataServiceContext.SaveChanges();
				_projectDataServiceContext.Detach(itemToDelete);
			}
			catch (Exception)
			{
				_projectDataServiceContext.Detach(itemToDelete); 
				return false;
			}
			return true;
		}

		/// <summary>
		/// tells the server that an unconditional delete needs to be performed i.e. go ahead and
		/// perform the requested operation irrespective of whether someone has changed the entity in the store.
		/// </summary>
		/// <param name="itemToDelete">The item to delete.</param>
		/// <returns></returns>
		public bool DeleteUnconditional(ConfigurationDataModel itemToDelete)
		{
			if(itemToDelete == null) return false;
			_projectDataServiceContext.AttachTo(AzureTableInitialization.ConfigurationTableName, itemToDelete, "*"); //"*" = unconditional update
			try
			{
				//var results = (from c in _projectDataServiceContext.ConfigurationTable
				//               where c.PartitionKey == itemToDelete.PartitionKey &&
				//                     c.RowKey == itemToDelete.RowKey
				//               select c).FirstOrDefault();

				_projectDataServiceContext.DeleteObject(itemToDelete);
				_projectDataServiceContext.SaveChanges();
				_projectDataServiceContext.Detach(itemToDelete);
			}
			catch (Exception)
			{
				_projectDataServiceContext.Detach(itemToDelete);
				return false;		// can't find a row to delete so return
			}

			return true;
		}

		#endregion

	}
}


